package pruebaciudad.formas;

import pruebaciudad.excepciones.ExcepcionGeometrica;

/**
 * Esta clase representa un polígono regular, para esta clase se está utilizando
 * un patrón de programación conocido como <b><tt>Factory</tt></b>, en el cuál
 * el constructor es privado y la generación de nuevos objetos se hace por medio 
 * de métodos <code>static</code>
 */
public class PoligonoRegular extends FiguraConCentro {
    private int radio;
    
    /**
     * El constructor es privado de manera que solo la clase PoligonoRegular
     * pueda generar instancias de si misma
     * 
     * @param lados cantidad de lados del polígono.
     */
    private PoligonoRegular(int lados){
        super(lados);
    }
      
    /**
     * Crea un nuevo triángulo centrado en el origen con el primer punto en la 
     * intersección del eje <tt>x</tt> y la circunferencia.
     * 
     * @param radio radio del circulo que circunscribe al triángulo
     * 
     * @return El nuevo triángulo creado
     */
    public static PoligonoRegular crearTriangulo(int radio){
        return crearTriangulo(radio, 0);
    }
      
    /**
     * Crea un nuevo triángulo centrado en el origen
     * 
     * @param radio radio del circulo que circunscribe al triángulo
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del triángulo y el primer punto
     * 
     * @return El nuevo triángulo creado
     */
    public static PoligonoRegular crearTriangulo(int radio, int angulo){
        return crearTriangulo(Punto.ORIGEN, radio, 0);
    }
      
    /**
     * Crea un nuevo triángulo
     * 
     * @param centro punto que representa al centro del triángulo
     * @param radio radio del circulo que circunscribe al triángulo
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del triángulo y el primer punto
     * 
     * @return El nuevo triángulo creado
     */
    public static PoligonoRegular crearTriangulo(Punto centro, int radio, int angulo){
        return crearPoligonoRegular(3, centro, radio, angulo);
    }
    
    /**
     * Crea un nuevo cuadrado centrado en el origen con el primer punto en la 
     * intersección del eje <tt>x</tt> y la circunferencia.
     * 
     * @param radio radio del circulo que circunscribe al cuadrado
     * 
     * @return El nuevo cuadrado creado
     */
    public static PoligonoRegular crearCuadrado(int radio){
        return crearCuadrado(radio, 0);
    }
      
    /**
     * Crea un nuevo cuadrado centrado en el origen
     * 
     * @param radio radio del circulo que circunscribe al cuadrado
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del cuadrado y el primer punto
     * 
     * @return El nuevo cuadrado creado
     */
    public static PoligonoRegular crearCuadrado(int radio, int angulo){
        return crearCuadrado(Punto.ORIGEN, radio, 0);
    }
    
    /**
     * Crea un nuevo cuadrado
     * 
     * @param centro punto que representa al centro del cuadrado
     * @param radio radio del circulo que circunscribe al cuadrado
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del cuadrado y el primer punto
     * 
     * @return El nuevo cuadrado creado
     */
    public static PoligonoRegular crearCuadrado(Punto centro, int radio, int angulo){
        return crearPoligonoRegular(4, centro, radio, angulo);
    }
    
    /**
     * Crea un nuevo pentagono centrado en el origen con el primer punto en la 
     * intersección del eje <tt>x</tt> y la circunferencia.
     * 
     * @param radio radio del circulo que circunscribe al pentagono
     * 
     * @return El nuevo pentagono creado
     */
    public static PoligonoRegular crearPentagono(int radio){
        return crearPentagono(radio, 0);
    }
    
    /**
     * Crea un nuevo pentágono centrado en el origen
     * 
     * @param radio radio del circulo que circunscribe al pentágono
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del pentágono y el primer punto
     * 
     * @return El nuevo pentágono creado
     */
    public static PoligonoRegular crearPentagono(int radio, int angulo){
        return crearPentagono(Punto.ORIGEN, radio, 0);
    }
    
    /**
     * Crea un nuevo pentágono
     * 
     * @param centro punto que representa al centro del pentágono
     * @param radio radio del circulo que circunscribe al pentágono
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del pentágono y el primer punto
     * 
     * @return El nuevo pentágono creado
     */
    public static PoligonoRegular crearPentagono(Punto centro, int radio, int angulo){
        return crearPoligonoRegular(5, centro, radio, angulo);
    }
    
    /**
     * Crea un nuevo hexágono centrado en el origen con el primer punto en la 
     * intersección del eje <tt>x</tt> y la circunferencia.
     * 
     * @param radio radio del circulo que circunscribe al hexágono
     * 
     * @return El nuevo hexágono creado
     */
    public static PoligonoRegular crearHexagono(int radio){
        return crearHexagono(radio, 0);
    }
    
    /**
     * Crea un nuevo hexágono centrado en el origen
     * 
     * @param radio radio del circulo que circunscribe al hexágono
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del hexágono y el primer punto
     * 
     * @return El nuevo hexágono creado
     */
    public static PoligonoRegular crearHexagono(int radio, int angulo){
        return crearHexagono(Punto.ORIGEN, radio, 0);
    }
    
    /**
     * Crea un nuevo hexágono
     * 
     * @param centro punto que representa al centro del hexágono
     * @param radio radio del circulo que circunscribe al hexágono
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro del hexágono y el primer punto
     * 
     * @return El nuevo hexágono creado
     */
    public static PoligonoRegular crearHexagono(Punto centro, int radio, int angulo){
        return crearPoligonoRegular(6, centro, radio, angulo);
    }
        
    /**
     * Crea un polígono regular de n lados con centro en el punto especificado.
     * 
     * @param lados cantidad de lados de la figura
     * @param centro punto que representa al centro de la figura
     * @param radio radio del circulo que circunscribe a la figura
     * 
     * <i>Nota:</i> el primer punto de esta figura ubica en la intersección 
     * de la circunferencia y el eje <tt>x</tt>
     * 
     * @return El nuevo polígono creado
     */
    public static PoligonoRegular crearPoligonoRegular(int lados, Punto centro, int radio){
        return crearPoligonoRegular(lados, centro, radio, 0);
    }
    
    /**
     * Crea un polígono regular de n lados con centro en el punto especificado.
     * 
     * @param lados cantidad de lados de la figura
     * @param centro punto que representa al centro de la figura
     * @param radio radio del circulo que circunscribe a la figura
     * @param angulo ángulo de desplazamiento
     * 
     * <i>Nota:</i> el ángulo es el que se forma entre el eje <tt>x</tt> y una 
     * recta que pasa por el centro de la figura y el primer punto
     * 
     * @return El nuevo polígono creado
     */
    public static PoligonoRegular crearPoligonoRegular(int lados, Punto centro, int radio, double angulo){
        if (radio <= 0){
            throw new ExcepcionGeometrica("El radio debe ser mayor a cero!");
        }
        if (lados <= 2){
            throw new ExcepcionGeometrica("Debe tener al menos 3 lados!");
        }
        
        PoligonoRegular forma = new PoligonoRegular(lados);
        int x = centro.getX();
        int y = centro.getY();
        
        angulo = Math.toRadians(angulo);
        double temp = 2 * Math.PI / lados;     
        
        for (int i = 0; i < lados; i++){
            Punto punto = new Punto((int)Math.round(x + Math.cos(temp * i + angulo) * radio), 
                                    (int)Math.round(y + Math.sin(temp * i + angulo) * radio));
            forma.agregarPunto(punto);
        }
        
        forma.setCentro(centro);
        
        return forma;
    }

    /**
     * @return radio del círculo que circunscribe al polígono
     */
    public int getRadio() {
        return radio;
    }
    
    /**
     * Calcula el area de un polígono regular
     * 
     * @return area
     */
    public double getArea(){
        double area = 0;
        
        for (int i = 0, n = getNumeroDePuntos() - 1; i <= n; i++){
            area += getAreaDelTriangulo(getCentro(), puntos[i], puntos[i == n ? 0 : i+1]);
        }
        
        return area;
    }
    
    /**
     * Función auxiliar que calcula el area de un triángulo formado por tres 
     * puntos
     * 
     * @param p1
     * @param p2
     * @param p3
     * 
     * @return area del triángulo
     */
    private double getAreaDelTriangulo(Punto p1, Punto p2, Punto p3){
        double perimetro = getPerimetroDelTriangulo(p1, p2, p3);
        double area = perimetro / 2;
        
        area *= perimetro / 2 - p1.distanciaA(p2);
        area *= perimetro / 2 - p1.distanciaA(p3);
        area *= perimetro / 2 - p3.distanciaA(p2);
        
        return Math.sqrt(area);
    }
    
    /**
     * Método auxiliar que calcula el perímetro de un triangulo formado por 3
     * puntos
     * 
     * @param p1
     * @param p2
     * @param p3
     * 
     * @return perímetro de un triángulo
     */
    private double getPerimetroDelTriangulo(Punto p1, Punto p2, Punto p3){
        return p1.distanciaA(p2) + p2.distanciaA(p3) + p3.distanciaA(p1);
    }
    
    /**
     * Calcula el perímetro de la figura
     * 
     * @return perímetro
     */
    public double getPerimetro(){
        double perimetro = puntos[0].distanciaA(puntos[puntos.length - 1]);
        
        for (int i = 0, n = getNumeroDePuntos() - 1; i < n; i++){
            perimetro += puntos[i].distanciaA(puntos[i + 1]);
        }
        
        return perimetro;
    }

}
